/*
 * Anything: A flexible and easy to use data structure that combines the 
 * characteristics of a list and a hashmap into one data structure.
 * 
 * Based on a paper by Peter Sommerlad and Marcel Rueedi on 
 * 'Do-it-yourself reflection' entered at EuroPLOP 1998.
 * 
 * This library was written completely from scratch and is based only 
 * in spirit on the original Java Anything implementation written by 
 * Andre Weinand/Marcel Rueedi in 1997/99 and enhanced by Stefan Tramm 
 * in 2006.
 * 
 * Copyright (c) 2008 Thomas Marti and others. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package any.incubator;

import static any.functor.Functions.asBigDecimal;
import static any.functor.Functions.asLong;
import static any.functor.Functions.asString;
import static any.functor.Functions.lastElement;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.StringTokenizer;

import any.Any;
import any.ImmutableAny;

/**
 * Possible Syntax (from rdxpl.org): 
 *   <fromNum> .. <toNum>
 *   <fromNum> .. <toNum> : <step>
 *   
 *   <exp1> , <exp2> , <expN> ... <lastExp> length <progressionLength>
 *   <exp1> , <exp2> , <expN> ... <lastExp>
 *   <exp1> , <exp2> , <expN> ... length <progressionLength>
 *
 */
public class NumberRange {
	@SuppressWarnings("unchecked")
	public static <T extends ImmutableAny> T parseRange(String str) {
		str = str.trim();
		BigDecimal start = BigDecimal.ZERO;
		BigDecimal end = BigDecimal.ZERO;
		BigDecimal stride = BigDecimal.ONE;
		long noOfStrides = Long.MAX_VALUE;

		StringTokenizer tokenizer = new StringTokenizer(str.trim(), ":#", true);
		Any tokens = Any.create.any(tokenizer);

		System.out.println();
		System.out.println(tokens);

		try {
			int nextIndex = 0;
			if (tokens.get(nextIndex).asBigDecimal(null) != null) {
				start = tokens.get(nextIndex).asBigDecimal(null);
				nextIndex++;
			}

			String delim = tokens.get(nextIndex).give(asString());
			nextIndex++;
			if (delim.equals(":")) {
				if (tokens.get(nextIndex, null).asBigDecimal(null) != null) {
					end = tokens.get(nextIndex).asBigDecimal(null);
					nextIndex++;
				}
				if (nextIndex < tokens.size()) {
					delim = tokens.get(nextIndex).give(asString());
					nextIndex++;
					if (delim.equals(":")) {
						stride = tokens.get(nextIndex).give(asBigDecimal());
					} else if (delim.equals("#")) {
						noOfStrides = tokens.get(nextIndex).give(asLong());
						stride = end.subtract(start).divide(BigDecimal.valueOf(noOfStrides), 10, BigDecimal.ROUND_FLOOR);
					} else {
						throw new RuntimeException("Expected '#' or ':', was: '" + delim + "'");
					}
				}
			} else if (delim.equals("#")) {
				noOfStrides = tokens.get(nextIndex).give(asLong());
				nextIndex++;
				if (nextIndex < tokens.size()) {
					delim = tokens.get(nextIndex).give(asString());
					nextIndex++;
					if (delim.equals(":")) {
						stride = tokens.get(nextIndex).give(asBigDecimal());
					} else {
						throw new RuntimeException("Expected ':', was: '" + delim + "'");
					}
				}
				end = start.add(stride.multiply(BigDecimal.valueOf(noOfStrides)));
			} else {
				throw new RuntimeException("Expected '#' or ':', was: '" + delim + "'");
			}
			if (noOfStrides <= 0) {
				throw new RuntimeException("Number of strides must be positive integer, was '" + noOfStrides + "'");
			}
		} catch (RuntimeException e) {
			throw new RuntimeException("Error while parsing string '" + str + "'", e);
		}

		boolean inBigInt = false;
		BigInteger startInt = null, endInt = null, strideInt = null;
		try {
			startInt = start.toBigIntegerExact();
			endInt = end.toBigIntegerExact();
			strideInt = stride.toBigIntegerExact();
			inBigInt = true;
		} catch (Exception e) {}

		Any any = Any.create.any();

		if (inBigInt) {
			long i = 0;
			for (BigInteger valInt = startInt; running(valInt, startInt, endInt) && i <= noOfStrides; valInt = valInt.add(strideInt), i++) {
				any.append(valInt);
			}
		} else {
			long i = 0;
			for (BigDecimal val = start; running(val, start, end) && i <= noOfStrides; val = val.add(stride), i++) {
				any.append(val);
			}
			if (noOfStrides < Long.MAX_VALUE && !end.equals( any.give(lastElement()).give(asBigDecimal()) )) {
				any.give(lastElement()).replaceValue(end);
			}
		}

		return (T)any;
	}

	@SuppressWarnings("unchecked")
	private static boolean running(Comparable value, Comparable start, Comparable end) {
		if (start.compareTo(end) <= 0) {
			return (value.compareTo(end) <= 0);
		} else {
			return (value.compareTo(end) >= 0);
		}
	}
}
